/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2021-2021. All rights reserved.
 */

package org.apache.ranger.obs.client;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.ipc.RemoteException;
import org.apache.hadoop.security.token.Token;
import org.apache.ranger.obs.security.authorization.AccessType;
import org.apache.ranger.obs.security.authorization.PermissionRequest;
import org.apache.ranger.obs.security.token.DelegationTokenIdentifier;
import org.apache.ranger.obs.security.token.MockSecretProvider;
import org.apache.ranger.obs.security.token.SimpleSecretManager;
import org.apache.ranger.obs.server.DistributedMiniServerBaseTest;
import org.apache.ranger.obs.server.ServerConstants;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;

/**
 * RangerObsClientImpl test
 *
 * @since 2021-08-16
 */
public class LoadBalanceRangerObsClientImplTest extends DistributedMiniServerBaseTest {

    private RangerObsClient rangerObsClient;

    private Configuration client_conf;

    private Configuration server1_conf;

    private String principal;

    @Before
    public void setUp() throws Exception {
        this.principal = "ranger/hadoop@EXAMPLE.COM";

        server1_conf = new Configuration();
        server1_conf.set("hadoop.security.authentication", "kerberos");
        server1_conf.set("ranger.obs.service.kerberos.principal", principal);
        server1_conf.set("ranger.obs.service.rpc.address", "127.0.0.1:26901");
        server1_conf.set("ranger.obs.service.status.port", "26900");
        server1_conf.set("ranger.obs.service.dt.secret.provider",
            "org.apache.ranger.obs.security.token.MockSecretProvider");
        server1_conf.set("ranger.obs.service.authorizer", "org.apache.ranger.obs.authorize.MockAuthorizer");

        client_conf = new Configuration();
        client_conf.set("ranger.obs.service.rpc.address", "127.0.0.1:26901;127.0.0.1:26902");
        client_conf.set("ranger.obs.service.kerberos.principal", principal);

        rangerObsClient = new LoadBalanceRangerObsClientImpl();
        rangerObsClient.init(client_conf);
    }

    @Test
    public void testGetCanonicalServiceName() {
        String serviceName = rangerObsClient.getCanonicalServiceName();
        Assert.assertEquals("testGetCanonicalServiceName", ClientConstants.DEFAULT_RANGER_OBS_SERVICE_DT_SERVICE_NAME,
            serviceName);
    }

    @Test
    public void testGetDelegationToken() throws IOException {
        String renewer = "yarn";
        Token<DelegationTokenIdentifier> token = (Token<DelegationTokenIdentifier>) rangerObsClient.getDelegationToken(
            renewer);
        String expect_kind = new Text("RANGER_OBS_SERVICE_DELEGATION_TOKEN").toString();

        DelegationTokenIdentifier dt_identifier = token.decodeIdentifier();

        Assert.assertEquals("testGetDelegationToken:kind", expect_kind, token.getKind().toString());
        Assert.assertEquals("testGetDelegationToken:service",
            ClientConstants.DEFAULT_RANGER_OBS_SERVICE_DT_SERVICE_NAME, token.getService().toString());
        Assert.assertEquals("testGetDelegationToken:renewer", renewer, dt_identifier.getRenewer().toString());
        Assert.assertEquals("testGetDelegationToken:owner", principal, dt_identifier.getOwner().toString());
    }

    @Test
    public void testGetDelegationTokenException() throws IOException {
        String renewer = "";
        Token<DelegationTokenIdentifier> token = (Token<DelegationTokenIdentifier>) rangerObsClient.getDelegationToken(
            renewer);
        String expect_kind = new Text("RANGER_OBS_SERVICE_DELEGATION_TOKEN").toString();

        DelegationTokenIdentifier dt_identifier = token.decodeIdentifier();

        Assert.assertEquals("testGetDelegationTokenException:kind", expect_kind, token.getKind().toString());
        Assert.assertEquals("testGetDelegationTokenException:service",
            ClientConstants.DEFAULT_RANGER_OBS_SERVICE_DT_SERVICE_NAME, token.getService().toString());
        Assert.assertEquals("testGetDelegationTokenException:renewer", renewer, dt_identifier.getRenewer().toString());
        Assert.assertEquals("testGetDelegationTokenException:owner", principal, dt_identifier.getOwner().toString());

        try {
            renewer = null;
            rangerObsClient.getDelegationToken(renewer);
        } catch (NullPointerException e) {
            Assert.assertTrue("testGetDelegationTokenException:renewer is null ", true);
        }
    }

    @Test
    public void testRenewDelegationToken() throws Exception {
        long now = System.currentTimeMillis();
        String renewer = "ranger";
        Token<DelegationTokenIdentifier> token = (Token<DelegationTokenIdentifier>) rangerObsClient.getDelegationToken(
            renewer);
        DelegationTokenIdentifier dt_identifier = token.decodeIdentifier();
        long issue = dt_identifier.getIssueDate();
        long max = dt_identifier.getMaxDate();
        int renew_interval = server1_conf.getInt("ranger.obs.service.dt.renew-interval",
            ServerConstants.DEFAULT_RANGER_OBS_SERVICE_DT_RENEW_INTERVAL);
        int max_lifetime = server1_conf.getInt("ranger.obs.service.dt.renew-interval",
            ServerConstants.DEFAULT_RANGER_OBS_SERVICE_DT_MAX_LIFETIME);
        Assert.assertTrue("testRenewDelegationToken:issue", issue >= now);
        Assert.assertTrue("testRenewDelegationToken:max", max >= (issue + max_lifetime));

        Thread.sleep(1000);
        now = System.currentTimeMillis();
        long next = rangerObsClient.renewDelegationToken(token, client_conf);
        Assert.assertTrue("testRenewDelegationToken:renew", next >= (now + renew_interval));
    }

    @Test
    public void testRenewDelegationTokenException() throws IOException, InterruptedException {
        //wrong DelegationTokenIdentifier
        Token<DelegationTokenIdentifier> token = new Token("wrong".getBytes(), "wrong".getBytes(), null, null);
        try {
            rangerObsClient.renewDelegationToken(token, client_conf);
        } catch (IOException e) {
            Assert.assertTrue("testRenewDelegationTokenException:wrong DelegationTokenIdentifier", true);
        }
        //renewer is null
        DelegationTokenIdentifier dtId = new DelegationTokenIdentifier(new Text("ranger/hadoop@EXAMPLE.COM"),
            new Text(), new Text("ranger/hadoop@EXAMPLE.COM"));
        token = new Token(dtId, new SimpleSecretManager(server1_conf));
        token.setService(new Text(ServerConstants.DEFAULT_RANGER_OBS_SERVICE_DT_SERVICE_NAME));
        try {
            rangerObsClient.renewDelegationToken(token, client_conf);
        } catch (IOException e) {
            Assert.assertEquals("testRenewDelegationTokenException:renewer is null",
                "org.apache.hadoop.security.AccessControlException", ((RemoteException) e).getClassName());
        }

        //renewer not match
        dtId = new DelegationTokenIdentifier(new Text("ranger/hadoop@EXAMPLE.COM"), new Text("yarn"),
            new Text("ranger/hadoop@EXAMPLE.COM"));
        token = new Token(dtId, new SimpleSecretManager(server1_conf));
        token.setService(new Text(ServerConstants.DEFAULT_RANGER_OBS_SERVICE_DT_SERVICE_NAME));
        try {
            rangerObsClient.renewDelegationToken(token, client_conf);
        } catch (IOException e) {
            Assert.assertEquals("testRenewDelegationTokenException:renewer not match",
                "org.apache.hadoop.security.AccessControlException", ((RemoteException) e).getClassName());
        }

        //MaxDate() < now
        Configuration conf = new Configuration();
        conf.setLong(ServerConstants.RANGER_OBS_SERVICE_DT_MAX_LIFETIME, 10);
        conf.set("ranger.obs.service.dt.secret.provider", "org.apache.ranger.obs.security.token.MockSecretProvider");
        dtId = new DelegationTokenIdentifier(new Text("ranger/hadoop@EXAMPLE.COM"), new Text("ranger"),
            new Text("ranger/hadoop@EXAMPLE.COM"));
        token = new Token(dtId, new SimpleSecretManager(conf));
        token.setService(new Text(ServerConstants.DEFAULT_RANGER_OBS_SERVICE_DT_SERVICE_NAME));
        try {
            Thread.sleep(1000);
            rangerObsClient.renewDelegationToken(token, client_conf);
        } catch (IOException e) {
            Assert.assertEquals("testRenewDelegationTokenException:MaxDate() < now",
                "org.apache.hadoop.security.token.SecretManager$InvalidToken", (((RemoteException) e).getClassName()));
        }

        //wrong password
        String old = MockSecretProvider.getSecret();
        MockSecretProvider.setSecret("wrong");
        dtId = new DelegationTokenIdentifier(new Text("ranger/hadoop@EXAMPLE.COM"), new Text("ranger"),
            new Text("ranger/hadoop@EXAMPLE.COM"));
        token = new Token(dtId, new SimpleSecretManager(server1_conf));
        token.setService(new Text(ServerConstants.DEFAULT_RANGER_OBS_SERVICE_DT_SERVICE_NAME));
        try {
            MockSecretProvider.setSecret(old);
            rangerObsClient.renewDelegationToken(token, client_conf);
        } catch (IOException e) {
            Assert.assertEquals("testRenewDelegationTokenException:wrong password",
                "org.apache.hadoop.security.AccessControlException", (((RemoteException) e).getClassName()));
        }

        //wrong service name
        dtId = new DelegationTokenIdentifier(new Text("ranger/hadoop@EXAMPLE.COM"), new Text("ranger"),
            new Text("ranger/hadoop@EXAMPLE.COM"));
        token = new Token(dtId, new SimpleSecretManager(server1_conf));
        token.setService(new Text("wrong"));
        try {
            rangerObsClient.renewDelegationToken(token, client_conf);
        } catch (IOException e) {
            Assert.assertTrue("testRenewDelegationTokenException:wrong service name", false);
        }

    }

    @Test
    public void testCancelDelegationToken() throws IOException {
        String renewer = "yarn";
        Token<DelegationTokenIdentifier> token = (Token<DelegationTokenIdentifier>) rangerObsClient.getDelegationToken(
            renewer);
        rangerObsClient.cancelDelegationToken(token, client_conf);
    }

    @Test
    public void testCancelDelegationTokenException() throws IOException {
        //wrong DelegationTokenIdentifier
        Token<DelegationTokenIdentifier> token = new Token("wrong".getBytes(), "wrong".getBytes(), null, null);
        try {
            rangerObsClient.cancelDelegationToken(token, client_conf);
        } catch (IOException e) {
            Assert.assertTrue("testCancelDelegationTokenException:InvalidToken", true);
        }
        //owner is null
        DelegationTokenIdentifier dtId = new DelegationTokenIdentifier(new Text(), new Text("yarn"), new Text());
        token = new Token(dtId, new SimpleSecretManager(server1_conf));
        token.setService(new Text(ServerConstants.DEFAULT_RANGER_OBS_SERVICE_DT_SERVICE_NAME));
        try {
            rangerObsClient.cancelDelegationToken(token, client_conf);
        } catch (IOException e) {
            Assert.assertEquals("testCancelDelegationTokenException:InvalidToken",
                "org.apache.hadoop.security.token.SecretManager$InvalidToken", (((RemoteException) e).getClassName()));
        }
        //canceller!=owner&&canceller!=renewer
        dtId = new DelegationTokenIdentifier(new Text("test/hadoop@EXAMPLE.COM"), new Text("yarn"),
            new Text("test/hadoop@EXAMPLE.COM"));
        token = new Token(dtId, new SimpleSecretManager(server1_conf));
        token.setService(new Text(ServerConstants.DEFAULT_RANGER_OBS_SERVICE_DT_SERVICE_NAME));
        try {
            rangerObsClient.cancelDelegationToken(token, client_conf);
        } catch (IOException e) {
            Assert.assertEquals("testCancelDelegationTokenException:AccessControlException",
                "org.apache.hadoop.security.AccessControlException", (((RemoteException) e).getClassName()));
        }
        //canceller!=owner&&renewer=null
        dtId = new DelegationTokenIdentifier(new Text("test/hadoop@EXAMPLE.COM"), new Text(),
            new Text("test/hadoop@EXAMPLE.COM"));
        token = new Token(dtId, new SimpleSecretManager(server1_conf));
        token.setService(new Text(ServerConstants.DEFAULT_RANGER_OBS_SERVICE_DT_SERVICE_NAME));
        try {
            rangerObsClient.cancelDelegationToken(token, client_conf);
        } catch (IOException e) {
            Assert.assertEquals("testCancelDelegationTokenException:AccessControlException",
                "org.apache.hadoop.security.AccessControlException", (((RemoteException) e).getClassName()));
        }
        //wrong service name
        dtId = new DelegationTokenIdentifier(new Text("ranger/hadoop@EXAMPLE.COM"), new Text("ranger"),
            new Text("ranger/hadoop@EXAMPLE.COM"));
        token = new Token(dtId, new SimpleSecretManager(server1_conf));
        token.setService(new Text("wrong"));
        try {
            rangerObsClient.cancelDelegationToken(token, client_conf);
        } catch (IOException e) {
            Assert.assertTrue("testCancelDelegationTokenException:AccessControlException", false);
        }
    }

    @Test
    public void testCheckPermission() throws IOException {
        PermissionRequest permissionReq = new PermissionRequest(AccessType.READ, "test", "test");
        boolean result = rangerObsClient.checkPermission(permissionReq);
        Assert.assertFalse("testCheckPermission", result);
    }

    @Test
    public void testCheckPermissionException() throws IOException {
        PermissionRequest permissionReq = new PermissionRequest(AccessType.READ, null, "test");
        try {
            rangerObsClient.checkPermission(permissionReq);
        } catch (NullPointerException e) {
            Assert.assertTrue("testCheckPermissionException:bucket is null ", true);
        }

        permissionReq = new PermissionRequest(AccessType.READ, "", "test");
        try {
            rangerObsClient.checkPermission(permissionReq);
        } catch (IOException e) {
            Assert.assertEquals("testCheckPermissionException:bucket is empty",
                "org.apache.hadoop.fs.InvalidRequestException", (((RemoteException) e).getClassName()));
        }

        permissionReq = new PermissionRequest(AccessType.READ, "test", null);
        try {
            rangerObsClient.checkPermission(permissionReq);
        } catch (NullPointerException e) {
            Assert.assertTrue("testCheckPermissionException:object is null ", true);
        }
        permissionReq = new PermissionRequest(AccessType.READ, "test", "");
        try {
            rangerObsClient.checkPermission(permissionReq);
        } catch (IOException e) {
            Assert.assertEquals("testCheckPermissionException:object is empty",
                "org.apache.hadoop.fs.InvalidRequestException", (((RemoteException) e).getClassName()));
        }
    }

    // @Test
    // public void testGetSTS() {
    // }

    @Test
    public void testInit() {
        Configuration client_conf = new Configuration();
        client_conf.set("ranger.obs.service.rpc.address", "xxx:9991;127.0.0.1:9992");
        client_conf.set("ranger.obs.service.kerberos.principal", principal);

        LoadBalanceRangerObsClientImpl rangerObsClient = new LoadBalanceRangerObsClientImpl();
        try {
            rangerObsClient.init(client_conf);
        } catch (IOException e) {
            Assert.assertTrue("testInit", false);
        }

        client_conf = new Configuration();
        client_conf.set("ranger.obs.service.kerberos.principal", principal);

        rangerObsClient = new LoadBalanceRangerObsClientImpl();
        try {
            rangerObsClient.init(client_conf);
        } catch (IOException e) {
            Assert.assertTrue("testInit", true);
        }
    }

    // @Test
    // public void testClose() {
    // }

}